Skip to content

sdl3 ttf font system - #1

Merged
HissingRat merged 11 commits into
mainfrom
codex/sdl3-ttf-font-system
Jun 23, 2026
Merged

sdl3 ttf font system#1
HissingRat merged 11 commits into
mainfrom
codex/sdl3-ttf-font-system

Conversation

@HissingRat

Copy link
Copy Markdown
Owner

No description provided.

- add SDL3_ttf fallback font discovery and macOS emoji rendering support
- align editor measurement, caret geometry, selection, and search with
  shaped text
- restore large-file editor loading/search performance
- normalize UI font sizing after SDL3_ttf migration
@HissingRat

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5825fb9795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/backends/text/sdl_ttf.zig Outdated
_ = c.TTF_SetTextWrapWhitespaceVisible(result, true);
}
if (cacheable) {
if (self.layouts.count() >= 2048) self.clearLayouts();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid evicting layouts while render holds them

When the cache is full, this clears every cached TTF_Text from inside layout() even if the caller is currently holding a cached Layout; for example render() can get a cache hit for the full text and then the CJK/emoji segment path calls layout() again for a segment. If that nested call crosses this threshold, it destroys the outer shaped.text and the subsequent caret/overlay/TTF draw calls use freed memory after enough distinct short strings have been rendered.

Useful? React with 👍 / 👎.

Comment thread src/backends/text/sdl_ttf.zig Outdated
Comment on lines +505 to +506
if (has_emoji or align_cjk) {
try self.renderVisualTextSegments(options.font, text, options.text, start, scale, options.color, emoji_box, align_cjk);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict emoji segmenting to the overlay path

On Linux and Windows, any string containing an emoji enters renderVisualTextSegments(), which skips emoji clusters, but the only replacement drawing path is emojiTexture() and it returns null off macOS. That makes emoji disappear entirely on non-macOS builds instead of falling back to SDL_ttf's normal text draw; gate this branch on the macOS overlay being available or render the full text for other platforms.

Useful? React with 👍 / 👎.

Comment on lines 836 to 837
if (std.mem.lastIndexOf(u8, text[0..safe_start], query)) |idx| {
return .{ .start = idx, .end = idx + query.len };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep previous search non-overlapping

For overlapping matches such as text aaa with query aa, lastIndexOf returns byte 1, while next search, match counting, and replace-all all advance by query.len and only count byte 0. Pressing Previous can therefore select a match that the counter and replace-all logic don't recognize, producing inconsistent search state; keep the previous search on the same non-overlapping iteration as the rest of the editor.

Useful? React with 👍 / 👎.

Guard cached SDL_ttf layouts against eviction while borrowed, keep emoji segmentation scoped to the macOS overlay path, and align editor previous search with the non-overlapping match sequence used by count and replace.
@HissingRat

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7528028072

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/ui/workspace/terminal_panel.zig Outdated
}

if (isSingleWidthAscii(cell)) {
if (isSingleWidthAscii(cell) and dvui.currentWindow().text_engine == null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep ASCII terminal run batching with SDL_ttf

In the normal app after this commit, appInit installs the SDL_ttf text engine into window.text_engine, so this condition is false for every printable ASCII terminal cell. That disables the existing asciiRun batching and turns a dense ASCII terminal frame into one renderText/TTF layout per cell instead of per same-style run, which can make high-output terminal sessions sluggish. Keep the batching path for single-width ASCII with the custom engine and only fall back to per-cell rendering where shaping/fallback actually requires it.

Useful? React with 👍 / 👎.

Restore run batching for single-width ASCII terminal cells when SDL_ttf is installed, while keeping non-ASCII and wide cells on the per-cell path.
@HissingRat

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8d9604d4b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return anchor.y + @as(f32, @floatFromInt(local_rows)) * line_h;
}

return @as(f32, @floatFromInt(estimatedVisualRows(text[0..bounded], wrap_cols))) * line_h;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use shaped layout for remote-editor search scroll

When a search match is before any trusted byte_heights anchor, this falls back to counting estimated mono columns for the whole prefix. In remote editor files containing combining marks, emoji, RTL text, or other SDL3_ttf-shaped runs, those estimates can diverge from the actual wrap positions, so applyPendingSearchScroll can scroll to the wrong vertical location for offscreen matches even though the new font backend has the real caret/layout geometry available.

Useful? React with 👍 / 👎.

@HissingRat

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab1350fb18

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +993 to +997
if (!activeMatchValid(state, text, query)) {
state.search_active_index = 0;
state.search_has_match = false;
state.search_match_count = 0;
resetSearchStatsScan(state);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep match counts valid before selection

When a non-empty query has no active match (for example immediately after the user types in the search box, because clearSearchMatch() just ran), this branch resets the count to 0 and returns before starting the incremental scan. As a result, a file that already contains the query displays 0/0 until the user presses Find, which is a regression from the previous updateSearchStats() path that counted matches independently of the active selection.

Useful? React with 👍 / 👎.

@HissingRat

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bea991e30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +741 to +744
var dirty = state.dirty;
if (te.text_changed) {
if (!reloaded_snapshot) dirty = true;
markSearchStatsDirty(state);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recompute editor dirty state from the current buffer

When a user edits a ready file and then reverts the text back to exactly snapshot.content, this keeps dirty stuck from the previous frame because the new logic starts from state.dirty and only ever sets it to true on te.text_changed. The old comparison against snapshot.content cleared dirty in that scenario, so now closing the editor can keep prompting for unsaved changes even when the buffer matches the remote snapshot; compute dirty from current_text versus snapshot.content after processing events.

Useful? React with 👍 / 👎.

Comment thread src/backends/text/sdl_ttf.zig Outdated
else
(if (rtl) substring.rect.x else substring.rect.x + substring.rect.w));
return .{
.x = (x_px + emojiCompensationBefore(layout_text, text, byte_offset, emoji_box)) / scale,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit emoji caret compensation to the current visual line

On macOS emoji-overlay text, caretPoint adds compensation for every emoji before byte_offset in the entire buffer, but TTF_GetTextSubString returns an x coordinate relative to the caret's visual line. If an earlier line contains emoji, the caret x for later explicit or wrapped lines is shifted by that previous-line emoji delta, so multiline TextEntry/editor cursor placement and scroll-to-cursor geometry become horizontally wrong; only emoji on the same visual line before the caret should affect this x value.

Useful? React with 👍 / 👎.

@HissingRat
HissingRat merged commit f6d3101 into main Jun 23, 2026
1 check passed
@HissingRat
HissingRat deleted the codex/sdl3-ttf-font-system branch June 23, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant